/**
 * @author مۇختەرجان مەخمۇت (mutearjan mahmood)
 * @email yeganaaa@163.com`
 * @create date 2020-08-28 16:36:07
 * @modify date 2020-08-28 16:36:07
 * @desc [description]
 */

import {BridgeHostEvents, Completer, IRpcBridgeHost, IRpcServerHost, ServerHostEvents} from "dream_rpc";
import WebSocket, {Server} from "ws"
import {ClosingBySystemException, IClosingReason} from "dream_rpc/bin/Core/RpcBridge";

export class WebSocketServerHost implements IRpcServerHost<Server> {
    private handler: ServerHostEvents = null!;
    private waitForLaunch = Completer.create()
    public constructor(private creator: () => Promise<Server>) {

    }
    backend: Server = null!
    public async prepare(eventHandler: ServerHostEvents): Promise<void> {
        this.backend = await this.creator()
        this.handler = eventHandler
        this.backend.on("connection", async (socket, request) => {
            await this.waitForLaunch.promise
            const clientHost = new WebSocketBridgeHost(async () => socket)
            clientHost.createdByServer = true
            await this.handler.onClientConnected(clientHost)
        })
        this.backend.on("close", async () => {
            await this.waitForLaunch.promise
            await this.handler.onShutdown()
        })
        this.backend.on("listening", async () => {
            await this.waitForLaunch.promise
            await this.handler.onReady()
        })
        this.backend.on("error", async error => {
            await this.waitForLaunch.promise
            await this.handler.onError(error)
        })
        // this.backend.emit("listening") //For 'ws' library is not emit this event. //****** Now "WS" package updated and this is no longer needed.
        this.backend.emit("listening") //The author is changes this every day... hmmmmmmmmm... ...
    }
    public async launch(): Promise<void> {
        this.waitForLaunch.complete()
    }
    public async shutdown(): Promise<void> {
        const completer = Completer.create()
        this.backend.close(err => {
            if (err == null) completer.complete()
            else completer.error(err)
        })
        return completer.promise
    }
}
enum ConnectionState {
    OPENED = 0x01,
    CLOSED = 0x02
}
export class WebSocketBridgeHost implements IRpcBridgeHost<WebSocket> {
    public createdByServer = false
    public backend: WebSocket = null!;
    private handler: BridgeHostEvents = null!;
    private state = ConnectionState.CLOSED
    constructor(private creator: () => Promise<WebSocket>) {
    }

    public async close(reason: IClosingReason): Promise<void> {
        if (this.state == ConnectionState.OPENED) {
            this.state = ConnectionState.CLOSED
            this.backend.close()
            await this.handler.onClose()
        }
    }

    public async open(): Promise<void> {
        try {
            this.backend = await this.creator()
            this.backend.onopen = async event => {
                await this.handler.onReady()
                this.state = ConnectionState.OPENED
            }
            this.backend.onclose = async event => {
                if (this.state == ConnectionState.OPENED) {
                    this.state = ConnectionState.CLOSED
                    await this.handler.onClose()
                    return
                }
                await this.handler.onUnableToConnect()
            }
            this.backend.onerror = async event => {
                await this.handler.onError(event.error)
                if (this.state == ConnectionState.OPENED) await this.close(new ClosingBySystemException(event.error));
            }
            this.backend.onmessage = async event => {
                let data = event.data
                const typeName = data.constructor.name
                if (typeName == "Blob") data = await (data as any as Blob).arrayBuffer()
                await this.handler.onMessage(new Uint8Array(data as ArrayBuffer))
            }

            if (this.createdByServer) {
                this.backend.onopen({target: this.backend})
            }
        } catch (error) {
            await this.handler.onError(error)
            await this.handler.onUnableToConnect()
        }
    }

    public async prepare(eventHandler: BridgeHostEvents): Promise<void> {
        this.handler = eventHandler
    }

    public send(data: Uint8Array): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            let isBrowserCompatibleEnvironment = true
            try {
                const isBrowser = window != null
                isBrowserCompatibleEnvironment = isBrowser
            } catch(error) {
                isBrowserCompatibleEnvironment = false
            }
            this.backend.send(data, err => {
                if (err != null) {
                    reject(err)
                    return
                }
                resolve()
            })
            if (isBrowserCompatibleEnvironment) resolve()
        })
    }
}

